#include "videosurface.h"
#include <QObject>
#include <QtGui/QPainter>
#include <QtGui/QImage>
#include <QDebug>

#include "../deskcore/video/videosource.h"
#include "../deskcore/util/common.h"
#include "../deskcore/rfb/rfbproto.h"
#include "../deskcore/ToxDeskApp.h"
#include "../deskcore/core/receiptnum.h"
#include "../deskcore/core/toxstring.h"
#include "../deskcore/core/core.h"
#include "../deskcore/core/coreav.h"
#include "../deskcore/friendlist.h"
#include "../deskcore/model/friend.h"


VideoSurface:: VideoSurface(QQuickItem *parent)
    : QQuickPaintedItem(parent)
    , source{ nullptr }
    , lastFrame{nullptr}
    , frameLock{ false }
    , hasSubscribed{ 0 }
    , ratio{ 1.0f }
    , expanding{ false }
{

    setAcceptedMouseButtons(Qt::AllButtons);
    setAcceptHoverEvents(true);
    setRenderTarget(QQuickPaintedItem::FramebufferObject);
    setOpaquePainting(false);

    setFlag(ItemAcceptsInputMethod, true);
}



float getSizeRatio(const QSizeF size)
{
    return size.width() / size.height();
}


void VideoSurface::paint(QPainter *painter)
{
    lock();
    painter->setRenderHint(QPainter::SmoothPixmapTransform);
    QRectF rcClient(0,0,this->size().width(),this->size().height()) ;
    //qDebug() << "paint......"<< rcClient;
    painter->fillRect(painter->viewport(), Qt::black);


    if (lastFrame) {

        QImage frame((const uchar *)lastFrame->img, lastFrame->w, lastFrame->h,QImage::Format_RGB32);
        painter->drawImage(boundingRect, frame, frame.rect(), Qt::NoFormatConversion);

    }

    unlock();
}

bool VideoSurface::isExpanding() const
{
    return expanding;
}

void VideoSurface::setSource(int friendId)
{
    this->friendId = friendId;
    unsubscribe();
    auto core = ToxDeskApp::getCore();
    CoreAV* av = core->getAv();
    if (av != nullptr) {
        source = av->getVideoSourceFromCall(friendId);
    }
    subscribe();
}

void VideoSurface::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry)
{
    recalulateBounds();
}

QRectF VideoSurface::getBoundingRect() const
{
    //QRectF bRect = boundingRect;
    //bRect.setBottomRight(boundingRect.bottom() + 1, boundingRect.right() + 1);
    return boundingRect;
}

float VideoSurface::getRatio() const
{
    return ratio;
}

void VideoSurface::subscribe()
{
    if (source && hasSubscribed++ == 0) {
        source->subscribe();
        source->frameAvailable.connect(&VideoSurface::onNewFrameAvailable, this);
        source->sourceStopped.connect(&VideoSurface::onSourceStopped, this);
    }
}

void VideoSurface::unsubscribe()
{
    if (!source || hasSubscribed == 0)
        return;

    if (--hasSubscribed != 0)
        return;

    lock();
    if (lastFrame != nullptr) {
        free(lastFrame->img);
        free(lastFrame);
        lastFrame = nullptr;
    }
    unlock();

    ratio = 1.0f;
    recalulateBounds();


    source->frameAvailable.disconnect(&VideoSurface::onNewFrameAvailable, this);
    source->sourceStopped.disconnect(&VideoSurface::onSourceStopped, this);
    source->unsubscribe();
}


void VideoSurface::onNewFrameAvailable(const std::shared_ptr<vpx_image_t> newFrame)
{
    lock();
    //qDebug()<<"onNewFrameAvailable......";
    newSize = QSizeF(newFrame->d_w, newFrame->d_h);
    if (lastFrame != nullptr) {
        if (lastFrame->w != newFrame->d_w || lastFrame->h != newFrame->d_h) {
            free(lastFrame->img);
            free(lastFrame);
            lastFrame = (FRAME_PKG*)malloc(sizeof(FRAME_PKG));
            lastFrame->w = newFrame->d_w;
            lastFrame->h = newFrame->d_h;
            size_t size = newFrame->d_w * newFrame->d_h * 4;
            lastFrame->size = size;
            lastFrame->img = malloc(size);
        }
    }
    else {
        lastFrame = (FRAME_PKG*)malloc(sizeof(FRAME_PKG));
        lastFrame->w = newFrame->d_w;
        lastFrame->h = newFrame->d_h;
        size_t size = newFrame->d_w * newFrame->d_h * 4;
        lastFrame->size = size;
        lastFrame->img = malloc(size);
    }

    yuv420tobgr(lastFrame->w, lastFrame->h, newFrame->planes[0], newFrame->planes[1], newFrame->planes[2],
            newFrame->stride[0], newFrame->stride[1], newFrame->stride[2], (uint8_t*)lastFrame->img);
    unlock();

 #if 0
    BITMAPINFOHEADER   bi;

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = lastFrame->w;
    bi.biHeight = lastFrame->h * (-1);
    bi.biPlanes = 1;
    bi.biBitCount = 32;//should get from system color bits
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    //save bmp to test
    BITMAPFILEHEADER bf;
    bf.bfType = 0x4d42;
    bf.bfReserved1 = 0;
    bf.bfReserved2 = 0;
    bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    bf.bfSize = bf.bfOffBits + lastFrame->w * lastFrame->w * 4;

    FILE* fp = fopen("e:\\save.bmp", "wb+");

    fwrite(&bf, 1, sizeof(bf), fp);
    fwrite(&bi, 1, sizeof(bi), fp);
    fwrite(lastFrame->img, 1, lastFrame->size, fp);

    fflush(fp);
    fclose(fp);
#endif

    float newRatio = getSizeRatio(newSize);

    if (!qFuzzyCompare(newRatio, ratio)) {
        ratio = newRatio;
        recalulateBounds();
    }
    //GetRGBPixels((AVFrame*)lastFrame->getAVFrame(newSize, AV_PIX_FMT_RGB24, false), buffer);

    QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);

}

void VideoSurface::onSourceStopped()
{
    lock();
    if (lastFrame != nullptr) {
        free(lastFrame->img);
        free(lastFrame);
        lastFrame = nullptr;
    }
    unlock();

    QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
}

bool VideoSurface::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::KeyPress:
    case QEvent::KeyRelease:
        //m_viewerAdapter.keyEventHandler(static_cast<QKeyEvent*>(event));
        return event->isAccepted();
    case QEvent::MouseButtonDblClick:
    case QEvent::MouseButtonPress:
    case QEvent::MouseButtonRelease:
    case QEvent::MouseMove:
        mouseEventHandler(static_cast<QMouseEvent*>(event));
        return true;
    case QEvent::HoverMove:
        //m_viewerAdapter.hoverEventHandler(static_cast<QHoverEvent*>(event));
        return true;
    case QEvent::Wheel:
        //m_viewerAdapter.wheelEventHandler(static_cast<QWheelEvent*>(event));
        return true;
    default:
        return QQuickItem::event(event);
    }
}

void VideoSurface::keyPressEvent(QKeyEvent *event)
{

}

void VideoSurface::keyReleaseEvent(QKeyEvent *event)
{

}

void VideoSurface::mousePressEvent(QMouseEvent *event)
{
    int mask = (((event->button() & Qt::LeftButton) ? rfbButton1Mask : 0) |
            ((event->button() & Qt::MiddleButton) ? rfbButton2Mask : 0) |
            ((event->button() & Qt::RightButton) ? rfbButton3Mask : 0));

    SubProcessPointerEvent(event->pos().x(),event->pos().y(),mask);
    //event->accept();
}

void VideoSurface::mouseMoveEvent(QMouseEvent *event)
{
    int mask = (((event->button() & Qt::LeftButton) ? rfbButton1Mask : 0) |
            ((event->button() & Qt::MiddleButton) ? rfbButton2Mask : 0) |
            ((event->button() & Qt::RightButton) ? rfbButton3Mask : 0));

    SubProcessPointerEvent(event->pos().x(),event->pos().y(),mask);
    //event->accept();
}

void VideoSurface::mouseReleaseEvent(QMouseEvent *event)
{

    int mask = (((event->button() & Qt::LeftButton) ? rfbButton1Mask : 0) |
            ((event->button() & Qt::MiddleButton) ? rfbButton2Mask : 0) |
            ((event->button() & Qt::RightButton) ? rfbButton3Mask : 0));

    SubProcessPointerEvent(event->pos().x(),event->pos().y(),mask);
    //event->accept();
}

void VideoSurface::hoverMoveEvent(QHoverEvent *event)
{
    QQuickPaintedItem::hoverMoveEvent(event);
    //event->accept();
     qDebug() <<"hoverMoveEvent"<< event ->position();
     int mask = (((event->button() & Qt::LeftButton) ? rfbButton1Mask : 0) |
             ((event->button() & Qt::MiddleButton) ? rfbButton2Mask : 0) |
             ((event->button() & Qt::RightButton) ? rfbButton3Mask : 0));

     SubProcessPointerEvent(event->position().x(),event->position().y(),mask);
}

void VideoSurface::wheelEvent(QWheelEvent *event)
{
    int mask = (((event->button() & Qt::LeftButton) ? rfbButton1Mask : 0) |
            ((event->button() & Qt::MiddleButton) ? rfbButton2Mask : 0) |
            ((event->button() & Qt::RightButton) ? rfbButton3Mask : 0));
    if (event->angleDelta().y() > 0)
        mask |= rfbButton4Mask;
    else if (event->angleDelta().y() < 0)
        mask |= rfbButton5Mask;
    SubProcessPointerEvent(event->position().x(),event->position().y(),mask);
    event->accept();
}

void VideoSurface::touchEvent(QTouchEvent *event)
{

}

void VideoSurface::keyEventHandler(QKeyEvent *e)
{

}

void VideoSurface::wheelEventHandler(QWheelEvent *event)
{

}

void VideoSurface::mouseEventHandler(QMouseEvent *event)
{
    int mask =0;
    if (event->type() != QEvent::MouseMove) {
        if ((event->type() == QEvent::MouseButtonPress) ||
                (event->type() == QEvent::MouseButtonDblClick)) {

             mask = (((event->button() & Qt::LeftButton) ? rfbButton1Mask : 0) |
                    ((event->button() & Qt::MiddleButton) ? rfbButton2Mask : 0) |
                    ((event->button() & Qt::RightButton) ? rfbButton3Mask : 0));
        } else if (event->type() == QEvent::MouseButtonRelease) {
             mask = (((event->button() & Qt::LeftButton) ? rfbButton1Mask : 0) |
                    ((event->button() & Qt::MiddleButton) ? rfbButton2Mask : 0) |
                    ((event->button() & Qt::RightButton) ? rfbButton3Mask : 0));
        }
    }

    SubProcessPointerEvent(event->pos().x(),event->pos().y(),mask);
}

void VideoSurface::hoverEventHandler(QHoverEvent *event)
{

}



void VideoSurface::recalulateBounds()
{
    if (expanding) {
        boundingRect = QRectF(0,0,this->size().width(),this->size().height());
    } else {
        QPoint pos;
        QSizeF size;
        QSizeF usableSize = this->size();
        int possibleWidth = usableSize.height() * ratio;

        if (possibleWidth > usableSize.width())
            size = (QSize(usableSize.width(), usableSize.width() / ratio));
        else
            size = (QSize(possibleWidth, usableSize.height()));

        pos.setX(width() / 2 - size.width() / 2);
        pos.setY(height() / 2 - size.height() / 2);
        boundingRect.setRect(pos.x(), pos.y(), size.width(), size.height());
    }

    //update();
    QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
}

void VideoSurface::lock()
{
    // Fast lock
    bool expected = false;
    while (!frameLock.compare_exchange_weak(expected, true))
        expected = false;
}

void VideoSurface::unlock()
{
    frameLock = false;
}


void VideoSurface::ProcessKeyEvent(int virtkey, uint64_t keyData)
{
#ifdef _WIN32
    bool down = ((keyData & 0x80000000l) == 0);

    if (!down) {
        std::map<uint8_t, KeyActionSpec>::iterator iter = pressedKeys.find(virtkey);

        if (iter == pressedKeys.end()) {
            //vnclog.Print(4, "Unexpected release of key code %d\n", virtkey);
            return;
        }

        KeyActionSpec syms = iter->second;
        for (int i = 0; syms.keycodes[i] != XK_VoidSymbol && i < MaxKeysPerKey;
             i++) {
            SendKeyEvent(syms.keycodes[i], false);
            //vnclog.Print(4, "Sent keysym %04x (release)\n", syms.keycodes[i]);
        }
        pressedKeys.erase(iter);
        return;
    }

    KeyActionSpec kas = m_keymap.PCtoX(virtkey, keyData);

    if (kas.releaseModifiers & KEYMAP_LCONTROL) {
        SendKeyEvent(XK_Control_L, false);
        //vnclog.Print(5, "fake L Ctrl released\n");
    }
    if (kas.releaseModifiers & KEYMAP_LALT) {
        SendKeyEvent(XK_Alt_L, false);
        //vnclog.Print(5, "fake L Alt released\n");
    }
    if (kas.releaseModifiers & KEYMAP_RCONTROL) {
        SendKeyEvent(XK_Control_R, false);
        //vnclog.Print(5, "fake R Ctrl released\n");
    }
    if (kas.releaseModifiers & KEYMAP_RALT) {
        SendKeyEvent(XK_Alt_R, false);
        //vnclog.Print(5, "fake R Alt released\n");
    }

    pressedKeys[virtkey] = kas;
    for (int i = 0; kas.keycodes[i] != XK_VoidSymbol && i < MaxKeysPerKey;
         i++) {
        SendKeyEvent(kas.keycodes[i], true);
        //vnclog.Print(4, "Sent keysym %04x (press)\n", kas.keycodes[i]);
    }

    if (kas.releaseModifiers & KEYMAP_RALT) {
        SendKeyEvent(XK_Alt_R, true);
        //vnclog.Print(5, "fake R Alt pressed\n");
    }
    if (kas.releaseModifiers & KEYMAP_RCONTROL) {
        SendKeyEvent(XK_Control_R, true);
        //vnclog.Print(5, "fake R Ctrl pressed\n");
    }
    if (kas.releaseModifiers & KEYMAP_LALT) {
        SendKeyEvent(XK_Alt_L, true);
        //vnclog.Print(5, "fake L Alt pressed\n");
    }
    if (kas.releaseModifiers & KEYMAP_LCONTROL) {
        SendKeyEvent(XK_Control_L, true);
        //vnclog.Print(5, "fake L Ctrl pressed\n");
    }
#endif
}
void VideoSurface::SendKeyEvent(uint32_t key, bool down)
{
    rfbKeyEventMsg ke;
    ke.type = rfbKeyEvent;
    ke.down = down ? 1 : 0;
    ke.key = key;

    auto core = ToxDeskApp::getCore();
    ReceiptNum receipt;
    ToxString data((uint8_t*)&ke, sz_rfbKeyEventMsg);
    core->sendActionData(friendId, data, receipt);
}

void VideoSurface::SubProcessPointerEvent(int x, int y, uint64_t keyflags)
{
   #ifdef _WIN32
    int mask = keyflags;

    //if (m_opts.m_SwapMouse)
    //	mask = (((keyflags & MK_LBUTTON) ? rfbButton1Mask : 0) |
    //		((keyflags & MK_MBUTTON) ? rfbButton3Mask : 0) |
    //		((keyflags & MK_RBUTTON) ? rfbButton2Mask : 0));
    //else
//    mask = (((keyflags & MK_LBUTTON) ? rfbButton1Mask : 0) |
//            ((keyflags & MK_MBUTTON) ? rfbButton2Mask : 0) |
//            ((keyflags & MK_RBUTTON) ? rfbButton3Mask : 0));

//    if ((short)HIWORD(keyflags) > 0)
//        mask |= rfbButton4Mask;
//    else if ((short)HIWORD(keyflags) < 0)
//        mask |= rfbButton5Mask;

    if (!boundingRect.contains(x, y))
        return;

    float fx = x - boundingRect.left();
    float fy = y - boundingRect.top();

    x = fx / boundingRect.width() * newSize.width();
    y = fy / boundingRect.height() * newSize.height();

    SendPointerEvent(x, y, mask);

    if ((short)HIWORD(keyflags) != 0) {
        // Immediately send a "button-up" after mouse wheel event.
        mask &= !(rfbButton4Mask | rfbButton5Mask);
        SendPointerEvent(x, y, mask);
    }
#endif
}

void VideoSurface::SendPointerEvent(int x, int y, int buttonMask)
{
#ifdef _WIN32
    rfbPointerEventMsg pe;

    pe.type = rfbPointerEvent;
    pe.buttonMask = buttonMask;
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    pe.x = x;// Swap16IfLE(x);
    pe.y = y;// Swap16IfLE(y);
    //std::cout << "xy:" << x << "," << y << std::endl;
    qDebug()<< "xy:" << x << "," << y;
    auto core = ToxDeskApp::getCore();
    ReceiptNum receipt;
    ToxString data((uint8_t*)&pe, sz_rfbPointerEventMsg);
    core->sendActionData(friendId, data, receipt);
#endif
}

